Shiny
Shiny es un paquete de R que facilita la creación de
aplicaciones web interactivas (apps) directamente desde R.
Estructura de una aplicación
Las aplicaciones se escriben en un script sencillo con nombre
app.R, el cual tiene dos componentes
- Un objeto de interfaz: ui()
- Una función del servidor: server()
El objeto ui() controla la apariencia de la
aplicación, mientras que la función server() contiene
las instrucciones para construir la aplicación.
library(shiny)
ui <-
server <-
shinyApp(ui = ui, server = server)
Crear un aplicación Shiny
Una vez abierto Shiny Web app aparecerĆ” la siguiente
ventana, en donde debemos especifivar la ruta en dónde se crearÔ la
carpeta que contendrĆ” el proyecto.
- Single File (app.R): crearĆ” Ui y Server en un solo archivo.
- Single File (ui.R/server.R): se crearƔn ambos en dos archivos
separados.
Ejemplos
Descargar la carpeta Ejemplo_histograma. Con
āsetwd()ā setear la ruta en donde se encuentra la carpeta. Por Ćŗltimo
correr el siguiente script.
library(shiny)
runApp("Ejemplo_histograma")
Otra manera de ejecutarla, es abrir el script App.R
en el editor de RStudio, en donde aparecerÔ el botón Run App en
la parte superior derecha del editor.
Se pueden crear aplicaciones copiando y modificando aplicaciones e
Shiny ya existentes. La galerĆa Shiny contiene una amplia
variedad de apicaciones con sus correspondientes códigos. O también se
pueden utilizar los ejemplos predefinidos de Shiny que se indican a
continuación:
runExample("01_hello") # a histogram
runExample("02_text") # tables and data frames
runExample("03_reactivity") # a reactive expression
runExample("04_mpg") # global variables
runExample("05_sliders") # slider bars
runExample("06_tabsets") # tabbed panels
runExample("07_widgets") # help text and submit buttons
runExample("08_html") # Shiny app built from HTML
runExample("09_upload") # file upload wizard
runExample("10_download") # file download wizard
runExample("11_timer") # an automated timer
Por defecto, las aplicaciones Shiny se muestran en modo ānormalā, es
decir, sin el script. Para que se muestre junto a la aplicación el
script, se debe establecer en modo āshowcaseā de la siguiente
manera:
runApp("01_hello", display.mode = "showcase")
O también se puede ver el directorio dónde se sitúan los archivos con
los códigos de estos ejemplos. Para ello se debe ejecutar la siguiente
función;
system.file("examples", package="shiny")
Interfaz ui.R
Shiny usa la función fluidPage() para crear una
pantalla que se ajusta automƔticamente a las dimensiones de la ventana
del navegador. El diseƱo de la interfaz de usuario consiste en colocar
elementos en esta función.
Los dos elementos mƔs comunes que se adicionan a
fluidPage() son titlePanel() y sidebarLayout().
sidebarLayout() tiene dos argumentos:
La función sidebarPanel(): panel lateral izquierdo
La función mainPanel(): panel lateral derecho, en donde en
general se muestra los resultados.
Se puede mover hacia el lado derecho dando el argumento opcional
position = ārightā.
Salida reactiva
En una salida reactiva se puede agregar cualquier salida de R, la
cual responde automƔticamente cuando un usuario cambia el valor de un
widget.
Se pueden crear resultados reactivos en un proceso de dos pasos:
- Agregar un objeto R a la ui().
- Indicar a Shiny cómo construir el objeto en la función server(). El
objeto serĆ” reactivo al enlazarlo con un widget.
Server
Esto se debe hacer en la función server(), la cual crea un objeto de
nombre output que contiene todo el código necesario para actualizar los
objetos R de la aplicación.
Cada objeto R necesita tener su propia entrada, cuyo nombre debe
coincidir con el elemento reactivo que se creó en la ui().
Calendario
Con el paquete calendR se pueden crear mapas de
calor con calendarios mensuales o anuales. Se debe especificar un aƱo,
un mes, establecer gradient = TRUE y cargar los datos.
Los colores se pueden personalizar con el argumento
low.col (color para el valor mƔs bajo) y
special.col (color para el valor mƔs alto).
Hay que tener en cuenta que los datos deben ser de la misma longitud
que el nĆŗmero de dĆas del mes o del aƱo, segĆŗn se utilice.
library(calendR)
covid <- read_excel("fallecidos de covid.xlsx")
covid_2021 <- covid %>% filter(dia > "2020-12-31")
calendR(year = 2021,
special.days = covid_2021$total,
gradient = TRUE,
low.col = "white",
special.col = "#FF4600",
legend.pos = "right",
legend.title = "Cantida de fallecidos")

GalerƬa de
calendarios
googleVis
El paquete googleVis proporciona una interfaz entre R y las
herramientas de grƔficos de Google. El paquete proporciona interfaces
para, entre otras, las siguientes funciones:
- GrƔfico geogrƔfico
- GrÔfico de dispersión
- GrƔfico de barras
- Histograma
- GrƔfico de burbujas
- GrƔfico de velas
- GrƔfico de calendario
Permite a los usuarios crear pƔginas web con grƔficos interactivos
basados en marcos de datos de R. Si bien utiliza las herramientas de
grƔficos de Google, los datos se mantienen locales y no se suben a
Google
library(googleVis)
plotdata <- gvisCalendar(data=covid,
datevar="dia",
numvar="total",
options=list(
title="Fallecimientos por Coronavirus en Argentina",
calendar="{cellSize:10,
yearLabel:{fontSize:20, color:'#444444'},
focusedCellColor:{stroke:'red'}}",
width=700, height=400),
chartid="Calendar")
plot(plotdata)
Mapas
Una de las formas para realizar mapas es utilizando el paquete
ggplot2. Para el tratamiento de datos espaciales se
puede utilizar el paquete sf .
Antes de crear un grƔfico con ggplot2, es necesario tener en la
sesión de R los datos necesarios para generar el mapa. Por lo tanto se
deben descargar las coordenadas de los polĆginos del mapa a graficar.
Por lo tanto, es necesario buscar en la web los archivos que tengan las
coordenadas requeridas, que en general son .json.
En este ejemplo descargamos las coordenadas de la Ciudad de Buenos
Aires y graficamos el mapa
library(sf)
map_data <- st_read("https://cdn.buenosaires.gob.ar/datosabiertos/datasets/ministerio-de-educacion/barrios/barrios.geojson")
## Reading layer `barrios' from data source
## `https://cdn.buenosaires.gob.ar/datosabiertos/datasets/ministerio-de-educacion/barrios/barrios.geojson'
## using driver `GeoJSON'
## Simple feature collection with 48 features and 6 fields
## Geometry type: POLYGON
## Dimension: XY
## Bounding box: xmin: -58.53152 ymin: -34.70529 xmax: -58.33516 ymax: -34.52649
## Geodetic CRS: WGS 84
ggplot() +
geom_sf(data = map_data)
Haciendo uso del mapa graficamos las ubicaciones de las casas que se
encuentran en venta de la base de datos properati
datos <- read_excel("Properati2.xlsx")
df <- datos %>%
select(-c(start_date, end_date, created_on, title, description))
df <- df %>%
filter(
l1 == "Argentina",
l2 == "Capital Federal",
property_type == "PH",
operation_type == "Venta")
barrio_data <- df %>% filter(lon >= -35 & lon <= -33 & lat >= -58.55 & lat <= -58.30)
ggplot() +
geom_sf(data = map_data) + # Plot the map
geom_point(data = barrio_data, aes(x = lat, y = lon), color = "#E67E22", size = 0.5) +
labs(title = "Ubicación geogrÔfica de los PH", x = "Longitud", y = "Latitud") +
theme_bw() +
xlab("Longitud") +
ylab("Latitud") +
theme(axis.title.x = element_text(size = 8),
axis.title.y = element_text(size = 8))

Ahora vamos a graficar el precio promedio por barrio:
precio_promedio <- datos %>% group_by(l3) %>% summarise(promedio = mean(price)) %>%
rename(nombre = l3)
prec_promedio_capital <- inner_join(map_data, precio_promedio, by = "nombre")
ggplot(data = prec_promedio_capital) +
geom_sf(aes(fill = promedio))
Utilizamos la librerĆa plotly
library(plotly)
grafico_inter <- ggplot(data = prec_promedio_capital) +
geom_sf(aes(fill = promedio))
ggplotly(grafico_inter)
Ejercicio
Crear una aplicación Shiny similar a la que se muestra a
continuación:
runApp("Nombres_Argentina")
LS0tDQp0aXRsZTogIlNoaW55IC0gQ2FsZW5kYXJpb3MgLSBNYXBhcyINCm91dHB1dDoNCiAgIGh0bWxfZG9jdW1lbnQ6DQogICAgIHRvYzogeWVzDQogICAgIGNvZGUtZm9sZDogc2hvdw0KICAgICBjb2RlLXRvb2xzOiB0cnVlDQogICAgIHRvYy1sb2NhdGlvbjogbGVmdA0KICAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgIHRoZW1lOiBmbGF0bHkNCiAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogc2VudGVuY2UNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsIA0KICAgICAgICAgICAgICAgICAgICAgIGV2YWw9RkFMU0UpDQpvcHRpb25zKHdpZHRoID0gOTApDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmVhZHhsKQ0KYGBgDQoNCiMjIFNoaW55DQoNCjxjZW50ZXI+DQohW10oc2hpbnkuanBnKXt3aWR0aD0zMDBweH0NCjxicj4NCg0KPC9jZW50ZXI+DQoNClNoaW55IGVzIHVuIHBhcXVldGUgZGUgICoqUioqIHF1ZSBmYWNpbGl0YSBsYSBjcmVhY2nDs24gZGUgYXBsaWNhY2lvbmVzIHdlYiBpbnRlcmFjdGl2YXMgKGFwcHMpIGRpcmVjdGFtZW50ZSBkZXNkZSBSLiANCg0KIyMjIEVzdHJ1Y3R1cmEgZGUgdW5hIGFwbGljYWNpw7NuDQoNCjxicj4NCg0KTGFzIGFwbGljYWNpb25lcyBzZSBlc2NyaWJlbiBlbiB1biBzY3JpcHQgc2VuY2lsbG8gY29uIG5vbWJyZSAqKmFwcC5SKiosIGVsIGN1YWwgdGllbmUgZG9zIGNvbXBvbmVudGVzDQoNCi0gVW4gb2JqZXRvIGRlIGludGVyZmF6OiA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPnVpKCk8L3NwYW4+DQotIFVuYSBmdW5jacOzbiBkZWwgc2Vydmlkb3I6IDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+c2VydmVyKCk8L3NwYW4+DQoNCkVsIG9iamV0byAqKnVpKCkqKiBjb250cm9sYSBsYSBhcGFyaWVuY2lhIGRlIGxhIGFwbGljYWNpw7NuLCBtaWVudHJhcyBxdWUgbGEgZnVuY2nDs24gKipzZXJ2ZXIoKSoqIGNvbnRpZW5lIGxhcyBpbnN0cnVjY2lvbmVzIHBhcmEgY29uc3RydWlyIGxhIGFwbGljYWNpw7NuLg0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCnVpIDwtIA0Kc2VydmVyIDwtIA0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQ0KYGBgDQoNCjxicj4NCg0KIyMjIENyZWFyIHVuIGFwbGljYWNpw7NuIFNoaW55DQoNCjxicj4NCg0KPGNlbnRlcj4NCiFbXShzaGlueTEucG5nKXt3aWR0aD02MDBweH0NCjwvY2VudGVyPg0KDQo8YnI+DQoNClVuYSB2ZXogYWJpZXJ0byAqKlNoaW55IFdlYiBhcHAqKiBhcGFyZWNlcsOhIGxhIHNpZ3VpZW50ZSB2ZW50YW5hLCBlbiBkb25kZSBkZWJlbW9zIGVzcGVjaWZpdmFyIGxhIHJ1dGEgZW4gZMOzbmRlIHNlIGNyZWFyw6EgbGEgY2FycGV0YSBxdWUgY29udGVuZHLDoSBlbCBwcm95ZWN0by4gDQoNCjxjZW50ZXI+DQohW10oc2hpbnkyLnBuZyl7d2lkdGg9NjAwcHh9DQo8L2NlbnRlcj4NCg0KPGJyPg0KDQotIFNpbmdsZSBGaWxlIChhcHAuUik6IGNyZWFyw6EgVWkgeSBTZXJ2ZXIgZW4gdW4gc29sbyBhcmNoaXZvLiANCi0gU2luZ2xlIEZpbGUgKHVpLlIvc2VydmVyLlIpOiBzZSBjcmVhcsOhbiBhbWJvcyBlbiBkb3MgYXJjaGl2b3Mgc2VwYXJhZG9zLiANCg0KPGJyPg0KDQojIyMgRWplbXBsb3MNCg0KRGVzY2FyZ2FyIGxhIGNhcnBldGEgKipFamVtcGxvX2hpc3RvZ3JhbWEqKi4gQ29uICdzZXR3ZCgpJyBzZXRlYXIgbGEgcnV0YSBlbiBkb25kZSBzZSBlbmN1ZW50cmEgbGEgY2FycGV0YS4gUG9yIMO6bHRpbW8gY29ycmVyIGVsIHNpZ3VpZW50ZSBzY3JpcHQuIA0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCnJ1bkFwcCgiRWplbXBsb19oaXN0b2dyYW1hIikNCmBgYA0KDQo8YnI+DQoNCk90cmEgbWFuZXJhIGRlIGVqZWN1dGFybGEsIGVzIGFicmlyIGVsIHNjcmlwdCAqKkFwcC5SKiogZW4gZWwgZWRpdG9yIGRlIFJTdHVkaW8sIGVuIGRvbmRlIGFwYXJlY2Vyw6EgZWwgYm90w7NuICpSdW4gQXBwKiBlbiBsYSBwYXJ0ZSBzdXBlcmlvciBkZXJlY2hhIGRlbCBlZGl0b3IuDQoNCjxicj4NCg0KU2UgcHVlZGVuIGNyZWFyIGFwbGljYWNpb25lcyBjb3BpYW5kbyB5IG1vZGlmaWNhbmRvIGFwbGljYWNpb25lcyBlIFNoaW55IHlhIGV4aXN0ZW50ZXMuIExhIGdhbGVyw61hIFtTaGlueV0oaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dhbGxlcnkvKSBjb250aWVuZSB1bmEgYW1wbGlhIHZhcmllZGFkIGRlIGFwaWNhY2lvbmVzIGNvbiBzdXMgY29ycmVzcG9uZGllbnRlcyBjw7NkaWdvcy4gTyB0YW1iacOpbiBzZSBwdWVkZW4gdXRpbGl6YXIgbG9zIGVqZW1wbG9zIHByZWRlZmluaWRvcyBkZSBTaGlueSBxdWUgc2UgaW5kaWNhbiBhIGNvbnRpbnVhY2nDs246DQoNCmBgYHtyfQ0KcnVuRXhhbXBsZSgiMDFfaGVsbG8iKSAgICAgICMgYSBoaXN0b2dyYW0NCnJ1bkV4YW1wbGUoIjAyX3RleHQiKSAgICAgICAjIHRhYmxlcyBhbmQgZGF0YSBmcmFtZXMNCnJ1bkV4YW1wbGUoIjAzX3JlYWN0aXZpdHkiKSAjIGEgcmVhY3RpdmUgZXhwcmVzc2lvbg0KcnVuRXhhbXBsZSgiMDRfbXBnIikgICAgICAgICMgZ2xvYmFsIHZhcmlhYmxlcw0KcnVuRXhhbXBsZSgiMDVfc2xpZGVycyIpICAgICMgc2xpZGVyIGJhcnMNCnJ1bkV4YW1wbGUoIjA2X3RhYnNldHMiKSAgICAjIHRhYmJlZCBwYW5lbHMNCnJ1bkV4YW1wbGUoIjA3X3dpZGdldHMiKSAgICAjIGhlbHAgdGV4dCBhbmQgc3VibWl0IGJ1dHRvbnMNCnJ1bkV4YW1wbGUoIjA4X2h0bWwiKSAgICAgICAjIFNoaW55IGFwcCBidWlsdCBmcm9tIEhUTUwNCnJ1bkV4YW1wbGUoIjA5X3VwbG9hZCIpICAgICAjIGZpbGUgdXBsb2FkIHdpemFyZA0KcnVuRXhhbXBsZSgiMTBfZG93bmxvYWQiKSAgICMgZmlsZSBkb3dubG9hZCB3aXphcmQNCnJ1bkV4YW1wbGUoIjExX3RpbWVyIikgICAgICAjIGFuIGF1dG9tYXRlZCB0aW1lcg0KYGBgDQoNCjxicj4NCg0KUG9yIGRlZmVjdG8sIGxhcyBhcGxpY2FjaW9uZXMgU2hpbnkgc2UgbXVlc3RyYW4gZW4gbW9kbyDigJxub3JtYWzigJ0sIGVzIGRlY2lyLCBzaW4gZWwgc2NyaXB0LiBQYXJhIHF1ZSBzZSBtdWVzdHJlIGp1bnRvIGEgbGEgYXBsaWNhY2nDs24gZWwgc2NyaXB0LCBzZSBkZWJlIGVzdGFibGVjZXIgZW4gbW9kbyDigJxzaG93Y2FzZeKAnSBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOg0KDQpgYGB7cn0NCnJ1bkFwcCgiMDFfaGVsbG8iLCBkaXNwbGF5Lm1vZGUgPSAic2hvd2Nhc2UiKQ0KYGBgDQoNCjxicj4NCg0KTyB0YW1iacOpbiBzZSBwdWVkZSB2ZXIgZWwgZGlyZWN0b3JpbyBkw7NuZGUgc2Ugc2l0w7phbiBsb3MgYXJjaGl2b3MgY29uIGxvcyBjw7NkaWdvcyBkZSBlc3RvcyBlamVtcGxvcy4gUGFyYSBlbGxvIHNlIGRlYmUgZWplY3V0YXIgbGEgc2lndWllbnRlIGZ1bmNpw7NuOw0KDQpgYGB7cn0NCnN5c3RlbS5maWxlKCJleGFtcGxlcyIsIHBhY2thZ2U9InNoaW55IikNCmBgYA0KDQo8YnI+DQoNCiMjIyBJbnRlcmZheiB1aS5SDQoNCjxicj4NCg0KU2hpbnkgdXNhIGxhIGZ1bmNpw7NuICoqZmx1aWRQYWdlKCkqKiBwYXJhIGNyZWFyIHVuYSBwYW50YWxsYSBxdWUgc2UgYWp1c3RhIGF1dG9tw6F0aWNhbWVudGUgYSBsYXMgZGltZW5zaW9uZXMgZGUgbGEgdmVudGFuYSBkZWwgbmF2ZWdhZG9yLiBFbCBkaXNlw7FvIGRlIGxhIGludGVyZmF6IGRlIHVzdWFyaW8gY29uc2lzdGUgZW4gY29sb2NhciBlbGVtZW50b3MgZW4gZXN0YSBmdW5jacOzbi4NCg0KPGJyPg0KDQpMb3MgZG9zIGVsZW1lbnRvcyBtw6FzIGNvbXVuZXMgcXVlIHNlIGFkaWNpb25hbiBhICoqZmx1aWRQYWdlKCkqKiBzb24gPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij50aXRsZVBhbmVsKCk8L3NwYW4+IHkgPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij5zaWRlYmFyTGF5b3V0KCk8L3NwYW4+LiAgDQpzaWRlYmFyTGF5b3V0KCkgdGllbmUgZG9zIGFyZ3VtZW50b3M6DQoNCi0gTGEgZnVuY2nDs24gc2lkZWJhclBhbmVsKCk6IHBhbmVsIGxhdGVyYWwgaXpxdWllcmRvDQoNCi0gTGEgZnVuY2nDs24gbWFpblBhbmVsKCk6IHBhbmVsIGxhdGVyYWwgZGVyZWNobywgZW4gZG9uZGUgZW4gZ2VuZXJhbCBzZSBtdWVzdHJhIGxvcyByZXN1bHRhZG9zLiANCg0KU2UgcHVlZGUgbW92ZXIgaGFjaWEgZWwgbGFkbyBkZXJlY2hvIGRhbmRvIGVsIGFyZ3VtZW50byBvcGNpb25hbCAqKnBvc2l0aW9uID0gInJpZ2h0IioqLg0KDQo8YnI+DQoNCiMjIyBDb250cm9sZXMgV2lkZ2V0cyANCg0KU29uIGVsZW1lbnRvcyB3ZWIgY29uIGxvcyBxdWUgZWwgdXN1YXJpbyBwdWVkZSBpbnRlcmFjdHVhciwgcHJvcG9yY2lvbmFuZG8gdW5hIGZvcm1hIGRlIGludGVyY2FtYmlhciB2YWxvcmVzIHUgb3BjaW9uZXMuDQoNCkVuIGxhIGdhbGVyw61hIGRlIFtXaWRnZXRzXShodHRwczovL3NoaW55LnBvc2l0LmNvL3IvZ2FsbGVyeS93aWRnZXRzL3dpZGdldC1nYWxsZXJ5LykNCg0KUGFyYSBhZ3JlZ2FyIHVuIHdpZGdldCgpIGEgbGEgYXBsaWNhY2nDs24sIHNlIHB1ZWRlIGhhY2VyIGVuIHNpZGViYXJQYW5lbCgpIG8gZW4gbWFpblBhbmVsKCkgZGVudHJvIGRlbCBvYmpldG8gdWkuDQoNCkxvcyBwcmltZXJvcyBkb3MgYXJndW1lbnRvcyBwYXJhIGNhZGEgd2lkZ2V0IHNvbjoNCg0KLSBOb21icmUgcGFyYSBlbCB3aWRnZXQ6IGVsIHVzdWFyaW8gbm8gdmVyw6EgZXN0ZSBub21icmUsIHBlcm8gZXMgbmVjZXNhcmlvIGVuIGVsIGPDs2RpZ28sIHBhcmEgcG9kZXIgYWNlZGVyYSDDqWwuDQotIEV0aXF1ZXRhOiBzZXLDoSBlbCBub21icmUgcXVlIGVsIHVzdWFyaW8gcG9kcsOhIHZlciBlbiBsYSBhcGxpY2FjacOzbg0KDQo8YnI+DQoNCiMjIyBTYWxpZGEgcmVhY3RpdmENCg0KDQpFbiB1bmEgc2FsaWRhIHJlYWN0aXZhIHNlIHB1ZWRlIGFncmVnYXIgY3VhbHF1aWVyIHNhbGlkYSBkZSBSLCBsYSBjdWFsIHJlc3BvbmRlIGF1dG9tw6F0aWNhbWVudGUgY3VhbmRvIHVuIHVzdWFyaW8gY2FtYmlhIGVsIHZhbG9yIGRlIHVuIHdpZGdldC4NCg0KDQpTZSBwdWVkZW4gY3JlYXIgcmVzdWx0YWRvcyByZWFjdGl2b3MgZW4gdW4gcHJvY2VzbyBkZSBkb3MgcGFzb3M6DQoNCi0gQWdyZWdhciB1biBvYmpldG8gUiBhIGxhIHVpKCkuDQotIEluZGljYXIgYSBTaGlueSBjw7NtbyBjb25zdHJ1aXIgZWwgb2JqZXRvIGVuIGxhIGZ1bmNpw7NuIHNlcnZlcigpLiBFbCBvYmpldG8gc2Vyw6EgcmVhY3Rpdm8gYWwgZW5sYXphcmxvIGNvbiB1biB3aWRnZXQuDQoNCjxicj4NCg0KIyMjIFNlcnZlcg0KDQoNCkVzdG8gc2UgZGViZSBoYWNlciBlbiBsYSBmdW5jacOzbiBzZXJ2ZXIoKSwgbGEgY3VhbCBjcmVhIHVuIG9iamV0byBkZSBub21icmUgb3V0cHV0IHF1ZSBjb250aWVuZSB0b2RvIGVsIGPDs2RpZ28gbmVjZXNhcmlvIHBhcmEgYWN0dWFsaXphciBsb3Mgb2JqZXRvcyBSIGRlIGxhIGFwbGljYWNpw7NuLg0KDQpDYWRhIG9iamV0byBSIG5lY2VzaXRhIHRlbmVyIHN1IHByb3BpYSBlbnRyYWRhLCBjdXlvIG5vbWJyZSBkZWJlIGNvaW5jaWRpciBjb24gZWwgZWxlbWVudG8gcmVhY3Rpdm8gcXVlIHNlIGNyZcOzIGVuIGxhIHVpKCkuDQoNCjxicj4NCg0KIyMgQ2FsZW5kYXJpbw0KDQpDb24gZWwgcGFxdWV0ZSAqKmNhbGVuZFIqKiBzZSBwdWVkZW4gY3JlYXIgbWFwYXMgZGUgY2Fsb3IgY29uIGNhbGVuZGFyaW9zIG1lbnN1YWxlcyBvIGFudWFsZXMuIFNlIGRlYmUgZXNwZWNpZmljYXIgdW4gYcOxbywgdW4gbWVzLCBlc3RhYmxlY2VyIGdyYWRpZW50ID0gVFJVRSB5IGNhcmdhciBsb3MgZGF0b3MuIA0KDQpMb3MgY29sb3JlcyBzZSBwdWVkZW4gcGVyc29uYWxpemFyIGNvbiBlbCBhcmd1bWVudG8gKipsb3cuY29sKiogKGNvbG9yIHBhcmEgZWwgdmFsb3IgbcOhcyBiYWpvKSB5ICoqc3BlY2lhbC5jb2wqKiAoY29sb3IgcGFyYSBlbCB2YWxvciBtw6FzIGFsdG8pLiANCg0KSGF5IHF1ZSB0ZW5lciBlbiBjdWVudGEgcXVlIGxvcyBkYXRvcyBkZWJlbiBzZXIgZGUgbGEgbWlzbWEgbG9uZ2l0dWQgcXVlIGVsIG7Dum1lcm8gZGUgZMOtYXMgZGVsIG1lcyBvIGRlbCBhw7FvLCBzZWfDum4gc2UgdXRpbGljZS4NCg0KPGJyPg0KDQotIEVqZW1wbG86DQoNCmBgYHtyIGV2YWwgPSBUUlVFfQ0KbGlicmFyeShjYWxlbmRSKQ0KDQpjb3ZpZCA8LSByZWFkX2V4Y2VsKCJmYWxsZWNpZG9zIGRlIGNvdmlkLnhsc3giKQ0KDQpjb3ZpZF8yMDIxIDwtIGNvdmlkICU+JSBmaWx0ZXIoZGlhID4gIjIwMjAtMTItMzEiKQ0KDQpjYWxlbmRSKHllYXIgPSAyMDIxLA0KICAgICAgICBzcGVjaWFsLmRheXMgPSBjb3ZpZF8yMDIxJHRvdGFsLA0KICAgICAgICBncmFkaWVudCA9IFRSVUUsDQogICAgICAgIGxvdy5jb2wgPSAid2hpdGUiLA0KICAgICAgICBzcGVjaWFsLmNvbCA9ICIjRkY0NjAwIiwNCiAgICAgICAgbGVnZW5kLnBvcyA9ICJyaWdodCIsDQogICAgICAgIGxlZ2VuZC50aXRsZSA9ICJDYW50aWRhIGRlIGZhbGxlY2lkb3MiKSANCmBgYA0KDQpbR2FsZXLDrGEgZGUgY2FsZW5kYXJpb3NdKGh0dHBzOi8vci1jb2Rlci5jb20vY2FsZW5kYXItcGxvdC1yLykNCg0KIyMjIGdvb2dsZVZpcw0KDQoNCkVsIHBhcXVldGUgZ29vZ2xlVmlzIHByb3BvcmNpb25hIHVuYSBpbnRlcmZheiBlbnRyZSBSIHkgbGFzIGhlcnJhbWllbnRhcyBkZSBncsOhZmljb3MgZGUgR29vZ2xlLiBFbCBwYXF1ZXRlIHByb3BvcmNpb25hIGludGVyZmFjZXMgcGFyYSwgZW50cmUgb3RyYXMsIGxhcyBzaWd1aWVudGVzIGZ1bmNpb25lczoNCg0KLSBHcsOhZmljbyBnZW9ncsOhZmljbw0KLSBHcsOhZmljbyBkZSBkaXNwZXJzacOzbg0KLSBHcsOhZmljbyBkZSBiYXJyYXMNCi0gSGlzdG9ncmFtYQ0KLSBHcsOhZmljbyBkZSBidXJidWphcw0KLSBHcsOhZmljbyBkZSB2ZWxhcw0KLSBHcsOhZmljbyBkZSBjYWxlbmRhcmlvDQoNCjxicj4NCg0KUGVybWl0ZSBhIGxvcyB1c3VhcmlvcyBjcmVhciBww6FnaW5hcyB3ZWIgY29uIGdyw6FmaWNvcyBpbnRlcmFjdGl2b3MgYmFzYWRvcyBlbiBtYXJjb3MgZGUgZGF0b3MgZGUgUi4gU2kgYmllbiB1dGlsaXphIGxhcyBoZXJyYW1pZW50YXMgZGUgZ3LDoWZpY29zIGRlIEdvb2dsZSwgbG9zIGRhdG9zIHNlIG1hbnRpZW5lbiBsb2NhbGVzIHkgbm8gc2Ugc3ViZW4gYSBHb29nbGUNCg0KYGBge3IgZXZhbD1UUlVFfQ0KbGlicmFyeShnb29nbGVWaXMpDQoNCnBsb3RkYXRhIDwtIGd2aXNDYWxlbmRhcihkYXRhPWNvdmlkLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkYXRldmFyPSJkaWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG51bXZhcj0idG90YWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG9wdGlvbnM9bGlzdCgNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlPSJGYWxsZWNpbWllbnRvcyBwb3IgQ29yb25hdmlydXMgZW4gQXJnZW50aW5hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhbGVuZGFyPSJ7Y2VsbFNpemU6MTAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyTGFiZWw6e2ZvbnRTaXplOjIwLCBjb2xvcjonIzQ0NDQ0NCd9LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9jdXNlZENlbGxDb2xvcjp7c3Ryb2tlOidyZWQnfX0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGg9NzAwLCBoZWlnaHQ9NDAwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjaGFydGlkPSJDYWxlbmRhciIpDQpwbG90KHBsb3RkYXRhKQ0KYGBgDQoNCjxicj4NCg0KIyMgTWFwYXMNCg0KPGJyPg0KDQpVbmEgZGUgbGFzIGZvcm1hcyBwYXJhIHJlYWxpemFyIG1hcGFzIGVzIHV0aWxpemFuZG8gZWwgcGFxdWV0ZSAqKmdncGxvdDIqKi4gDQpQYXJhIGVsIHRyYXRhbWllbnRvIGRlIGRhdG9zIGVzcGFjaWFsZXMgc2UgcHVlZGUgdXRpbGl6YXIgZWwgcGFxdWV0ZSAqKnNmKiogLg0KDQo8YnI+DQoNCkFudGVzIGRlIGNyZWFyIHVuIGdyw6FmaWNvIGNvbiBnZ3Bsb3QyLCBlcyBuZWNlc2FyaW8gdGVuZXIgZW4gbGEgc2VzacOzbiBkZSBSIGxvcyBkYXRvcyBuZWNlc2FyaW9zIHBhcmEgZ2VuZXJhciBlbCBtYXBhLiBQb3IgbG8gdGFudG8gc2UgZGViZW4gZGVzY2FyZ2FyIGxhcyBjb29yZGVuYWRhcyBkZSBsb3MgcG9sw61naW5vcyBkZWwgbWFwYSBhIGdyYWZpY2FyLiBQb3IgbG8gdGFudG8sIGVzIG5lY2VzYXJpbyBidXNjYXIgZW4gbGEgd2ViIGxvcyBhcmNoaXZvcyBxdWUgdGVuZ2FuIGxhcyBjb29yZGVuYWRhcyByZXF1ZXJpZGFzLCBxdWUgZW4gZ2VuZXJhbCBzb24gKiouanNvbioqLg0KDQo8YnI+DQoNCi0gRWplbXBsbw0KDQoNCkVuIGVzdGUgZWplbXBsbyBkZXNjYXJnYW1vcyBsYXMgY29vcmRlbmFkYXMgZGUgbGEgQ2l1ZGFkIGRlIEJ1ZW5vcyBBaXJlcyB5IGdyYWZpY2Ftb3MgZWwgbWFwYQ0KDQpgYGB7ciBldmFsPVRSVUV9DQpsaWJyYXJ5KHNmKQ0KDQptYXBfZGF0YSA8LSBzdF9yZWFkKCJodHRwczovL2Nkbi5idWVub3NhaXJlcy5nb2IuYXIvZGF0b3NhYmllcnRvcy9kYXRhc2V0cy9taW5pc3RlcmlvLWRlLWVkdWNhY2lvbi9iYXJyaW9zL2JhcnJpb3MuZ2VvanNvbiIpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gbWFwX2RhdGEpDQpgYGANCjxicj4NCg0KSGFjaWVuZG8gdXNvIGRlbCBtYXBhIGdyYWZpY2Ftb3MgbGFzIHViaWNhY2lvbmVzIGRlIGxhcyBjYXNhcyBxdWUgc2UgZW5jdWVudHJhbiBlbiB2ZW50YSBkZSBsYSBiYXNlIGRlIGRhdG9zICoqcHJvcGVyYXRpKioNCg0KYGBge3IgZXZhbD1UUlVFfQ0KDQpkYXRvcyA8LSByZWFkX2V4Y2VsKCJQcm9wZXJhdGkyLnhsc3giKQ0KDQpkZiA8LSBkYXRvcyAlPiUNCiAgc2VsZWN0KC1jKHN0YXJ0X2RhdGUsIGVuZF9kYXRlLCBjcmVhdGVkX29uLCB0aXRsZSwgZGVzY3JpcHRpb24pKQ0KDQpkZiA8LSBkZiAlPiUNCiAgZmlsdGVyKA0KICAgIGwxID09ICJBcmdlbnRpbmEiLA0KICAgIGwyID09ICJDYXBpdGFsIEZlZGVyYWwiLA0KICAgIHByb3BlcnR5X3R5cGUgPT0gIlBIIiwNCiAgICBvcGVyYXRpb25fdHlwZSA9PSAiVmVudGEiKQ0KDQpiYXJyaW9fZGF0YSA8LSBkZiAlPiUgZmlsdGVyKGxvbiA+PSAtMzUgJiBsb24gPD0gLTMzICYgbGF0ID49IC01OC41NSAmIGxhdCA8PSAtNTguMzApDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gbWFwX2RhdGEpICsgICMgUGxvdCB0aGUgbWFwDQogIGdlb21fcG9pbnQoZGF0YSA9IGJhcnJpb19kYXRhLCBhZXMoeCA9IGxhdCwgeSA9IGxvbiksIGNvbG9yID0gIiNFNjdFMjIiLCBzaXplID0gMC41KSArDQogIGxhYnModGl0bGUgPSAiVWJpY2FjacOzbiBnZW9ncsOhZmljYSBkZSBsb3MgUEgiLCB4ID0gIkxvbmdpdHVkIiwgeSA9ICJMYXRpdHVkIikgKw0KICB0aGVtZV9idygpICsNCiAgeGxhYigiTG9uZ2l0dWQiKSArDQogIHlsYWIoIkxhdGl0dWQiKSArDQogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksDQogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpDQpgYGANCg0KPGJyPg0KDQpBaG9yYSB2YW1vcyBhIGdyYWZpY2FyIGVsIHByZWNpbyBwcm9tZWRpbyBwb3IgYmFycmlvOg0KDQoNCmBgYHtyIGV2YWw9VFJVRX0NCg0KcHJlY2lvX3Byb21lZGlvIDwtIGRhdG9zICU+JSBncm91cF9ieShsMykgJT4lIHN1bW1hcmlzZShwcm9tZWRpbyA9IG1lYW4ocHJpY2UpKSAlPiUNCiAgICAgICAgICAgICAgICAgICByZW5hbWUobm9tYnJlID0gbDMpDQoNCnByZWNfcHJvbWVkaW9fY2FwaXRhbCA8LSBpbm5lcl9qb2luKG1hcF9kYXRhLCBwcmVjaW9fcHJvbWVkaW8sIGJ5ID0gIm5vbWJyZSIpDQoNCmdncGxvdChkYXRhID0gcHJlY19wcm9tZWRpb19jYXBpdGFsKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBwcm9tZWRpbykpDQpgYGANCjxicj4NCg0KVXRpbGl6YW1vcyBsYSBsaWJyZXLDrWEgKipwbG90bHkqKg0KDQpgYGB7ciBldmFsPVRSVUV9DQpsaWJyYXJ5KHBsb3RseSkNCg0KZ3JhZmljb19pbnRlciA8LSBnZ3Bsb3QoZGF0YSA9IHByZWNfcHJvbWVkaW9fY2FwaXRhbCkgKw0KICBnZW9tX3NmKGFlcyhmaWxsID0gcHJvbWVkaW8pKQ0KDQpnZ3Bsb3RseShncmFmaWNvX2ludGVyKQ0KYGBgDQoNCjxicj4NCg0KIyMgRWplcmNpY2lvDQoNCkNyZWFyIHVuYSBhcGxpY2FjacOzbiBTaGlueSBzaW1pbGFyIGEgbGEgcXVlIHNlIG11ZXN0cmEgYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCnJ1bkFwcCgiTm9tYnJlc19BcmdlbnRpbmEiKQ0KYGBgDQoNCg==